package cn.echoparrot.domain.service;

import cn.echoparrot.domain.entity.DataFile;
import cn.echoparrot.domain.entity.Dataset;
import cn.echoparrot.domain.entity.FileInfo;
import cn.echoparrot.domain.entity.Segment;
import cn.echoparrot.domain.vo.NewDataset;
import cn.echoparrot.repository.neo4j.DataFileMapper;
import cn.echoparrot.repository.neo4j.DatasetMapper;
import org.neo4j.driver.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.LocalDateTime;
import java.util.*;

/**
 * 知识库相关文件的存储
 * 备注：保持这个类为一个轻量级对象，不要轻易增加对外部的依赖。只可依赖一些基本的外部工具库。
 * TODO 后续改为 由DatasetId作为文件夹路径，不再使用知识库名词在路径里。
 */
@Service
public class DefaultDatasetService implements DatasetService{

	private static final Logger logger = LoggerFactory.getLogger(DefaultDatasetService.class);

	/**
	 * 支持的文档格式，只有符合这些格式的文档才可被解析与处理。
	 * String suffix = FilenameUtils.getExtension(fileName);
	 */
	private static final List<String> SUPPORTED_FILE_TYPES = Arrays.asList("html", "pdf", "docx", "doc", "txt", "md", "xlsx", "xls", "png", "gif", "jpg", "jpeg");


	public static final String UPLOADS = "uploads";
	public static final String SEGMENTS = "segments";


	/**
	 * 由外部配置文件读取。
	 * 办法一：java.util.Properties + ClassLoader.getSystemResource
	 * 办法二：Spring注入：@Configuration &  @PropertySource("classpath:alibaba-config.properties")
	 */
	private final String catalog;

	private final DatasetMapper datasetMapper;
	private final DataFileMapper dataFileMapper;

	public DefaultDatasetService(@Value("${datasets.path:datasets}") String path, DatasetMapper datasetMapper, DataFileMapper dataFileMapper){
		Assert.notNull(path,"知识库文档的存储目录不能为空");
		Assert.notNull(datasetMapper, "知识库Mapper不能为空");
		Assert.notNull(dataFileMapper, "数据文件Mapper不能为空");
		this.catalog = Paths.get(path).toAbsolutePath().normalize().toString();
		this.datasetMapper = datasetMapper;
		this.dataFileMapper = dataFileMapper;
	}

	@Override
	public Path uploadPath(Integer userId, String datasetName,String fileName) {
		return Path.of(catalog, userId.toString(),datasetName, UPLOADS,  fileName);
	}

	@Override
	public Path segmentPath(Integer userId, String datasetName,String fileName) {
		return  Path.of(catalog, userId.toString(), datasetName, SEGMENTS,fileName+".xml");
	}


	private void mkdirs(String datasetName,Integer userId){
		String id = userId.toString();
		Path.of(catalog, id, datasetName).toFile().mkdirs();
		Path.of(catalog, id, datasetName,  UPLOADS).toFile().mkdirs();
		Path.of(catalog, id, datasetName, SEGMENTS).toFile().mkdirs();
	}

	private void deleteDatasetPath(Integer userId,String datasetName){
		Path datasetPath = Path.of(catalog,userId.toString(), datasetName);
		if(datasetPath.toFile().exists()){
            try {
                Files.walk(datasetPath).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(file -> file.delete());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
	}

	@Override
	public List<FileInfo> listUploads(Integer userId, String datasetName) throws IOException{
		Assert.hasLength(datasetName,"知识库名称不能为空");
		return Files.walk(Path.of(catalog, userId.toString(), datasetName,  UPLOADS)).filter(p->{
			String fileTyle = StringUtils.getFilenameExtension(p.toString());
			return SUPPORTED_FILE_TYPES.contains(fileTyle);
		}).filter(Files::isRegularFile).map(p -> {
			try {
				// 获取文件属性
				BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
				return new FileInfo(p.getFileName().toString(), attrs);
			} catch (IOException e) {
				// 获取文件属性时发生异常, 返回文件名和大小为0的DocumentFileInfo
				return new FileInfo(p.getFileName().toString());
			}
		}).toList(); // 只保留文件;
	}

	@Override
	public Dataset create(NewDataset newDataset,Integer userId, int dimensions) {
		mkdirs(newDataset.name(),userId);

		var dataset = new Dataset()
				.name(newDataset.name())
				.description(newDataset.description())
				.embeddingModel(newDataset.embeddingModel())
				.userId(userId)
				.createdAt(LocalDateTime.now());
		// 创建向量库
		var savedDataset =  datasetMapper.save( dataset);
		datasetMapper.createVectorIndex(savedDataset.indexName(), savedDataset.labelName4Segment(), dimensions);
		return savedDataset;
	}

	@Override
    public Dataset update(Dataset dataset)  {
        Optional<Dataset> old = datasetMapper.findById(dataset.id());
        if (old.isEmpty()) {
            throw new IllegalArgumentException("知识库不存在: " + dataset.id());
        }

        if(!old.get().name().equals(dataset.name())){
            // 获取原始目录路径
            Path oldPath = Path.of(catalog, old.get().userId().toString(), old.get().name());
            // 创建新目录路径
            Path newPath = Path.of(catalog, dataset.userId().toString(),dataset.name());
            
            // 重命名目录
            try {
                // 移动文件夹
                Files.move(oldPath, newPath);

            } catch (IOException e) {
                logger.error("重命名知识库目录失败: {} -> {}", old.get().name(), dataset.name(), e);
                throw new IllegalArgumentException("知识库改名失败，请检查文件权限和路径存在性");
            }
        }
		// 同步更新数据库记录
		Dataset updated = datasetMapper.update(dataset);
        return updated;
    }

	@Override
	public int delete(Long datasetId) throws IOException {
		// 确保在没有数据的情况下才能删除知识库。
		Assert.state(datasetId>=0, "知识库的ID不正确！");
		Optional<Dataset> dataset = datasetMapper.findById(datasetId);
		if(dataset.isEmpty()){
			throw new IllegalArgumentException("知识库不存在！");
		}
		Path datasetPath = Path.of(catalog,dataset.get().userId().toString(), dataset.get().name(), UPLOADS);
		// 查看该目录下是否存在文件
		if(Files.exists(datasetPath) && Files.list(datasetPath).findAny().isPresent()){
			throw new IllegalStateException("该知识库下存在文件，请先删除文件");
		}

		int deleted = datasetMapper.delete(datasetId);
		datasetMapper.deleteVectorIndex(dataset.get().indexName());
		logger.info("删除知识库: {},{}", datasetId, dataset.get().name());

		deleteDatasetPath(dataset.get().userId(),dataset.get().name());
		return deleted;
	}

	@Override
	public List<Dataset> listDatasets() {
		return datasetMapper.listDatasets();
	}

	@Override
	public List<String> supportedFileTypes(){
		return SUPPORTED_FILE_TYPES;
	}

	@Override
	public void delete(Long datasetId, String[] filenames) throws IOException {
		Optional<Dataset> datasetOptional =datasetMapper.findById(datasetId);
		if(datasetOptional.isEmpty()){
			throw new IllegalArgumentException("知识库不存在");
		}
		Dataset dataset = datasetOptional.get();
		for(String fileName:filenames) {
			Path path = uploadPath(dataset.userId(), dataset.name(), fileName);
			Files.deleteIfExists(path);
			dataFileMapper.deleteFile(datasetId, fileName);
		}
	}

	@Override
	public int addFile(Long datasetId, DataFile file) {
		// 通过Neo4jRepository的方式添加文件
		return dataFileMapper.save(file).segments().size();
	}

	@Override
	public List<Segment> similaritySearch(String indexName, int topK, float[] embeddingValue, double threshold){
		var embedding = Values.value(embeddingValue);
		return datasetMapper.similaritySearch(indexName, topK, embedding, threshold);
	}
}
